/*
 * Mandelbrot
 * Copyright (C) 1998 Ken Perlin
 * Copyright (C) 2009 John Pritchard
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package test;

import fv3tk.Animator;
import fv3tk.Fv3Screen;

import javax.media.opengl.GL2;

import com.sun.javafx.newt.KeyEvent;
import com.sun.javafx.newt.MouseEvent;

import java.nio.ByteBuffer;

/**
 * @author Ken Perlin
 * @author jdp@syntelos
 */
public class Mandelbrot
    extends fv3tk.Fv3Canvas
{
    public static void main(String[] argv){
        try {
            Mandelbrot gears = new Mandelbrot();
            Animator animator = new Animator(gears);
            animator.start();
        }
        catch (Exception exc){
            exc.printStackTrace();
            System.exit(1);
        }
    }
    /**
     * Default screen size overridden by JNLP definitions.
     */
    private final static int RES = 500;
    /**
     * Resolution limited to unit pixels
     */
    private final static int Z = 64;

    /*
     * Nothing volatile because it's all in the animator thread.
     */

    int pass = 0, limit = 256;
    double xOrigin = 0;
    double yOrigin = 0;
    double scale = .25;
    int N = 0;
    int[] sx, sy, ss;
    ByteBuffer buffer;
    Fv3Screen window;
    int windowWidth = RES, windowHeight = RES;
    double[] color = new double[3];


    public Mandelbrot(){
        super();
    }


    void initSxy(int _N) {
        N = _N;
        sx = new int[N*N];
        sy = new int[N*N];
        ss = new int[N*N];
        int b;
        for (int n = N*N-1 ; n >= 0 ; n--) {
            sx[n] = sy[n] = ss[n] = 0;
            for (int i = N/2 ; i > 0 ; i /= 2) {
                b = n/i/i+1;
                sx[n] += ((b/2) % 2) * N/i/2;
                sy[n] += ((b-1) % 2) * N/i/2;
                ss[n] = b==1 ? N/i : ss[n] > 1 ? ss[n] : 1;
            }
        }
    }
    void initBuffer() {

        buffer = ByteBuffer.allocate(windowWidth*windowHeight*4);
        for (N = 1 ; N * Z < windowWidth ; N *= 2);
        initSxy(N);
        pass = 0;
    }
    double mandelbrot(double x, double y) {
        double i=0,j=0,k=0,r=0;
        for (j=r*r-i*i+x,i=2*r*i+y; (i*i+j*j < 11)&&( k++ < limit); r=j){
            j=r*r-i*i+x;
            i=2*r*i+y;
        }
        return (double)k/limit;
    }

    void computeColor(double x, double y) {
        double t = mandelbrot(
                              (x - xOrigin)/scale - 2.5,
                              (y - yOrigin)/scale - 2.*windowHeight/windowWidth);
        t = (t > 1) ? 0 : t / 4;
        if (t < 1./16)
            t *= 16;
        else {
            t = 1 - (t-1./16) * 16./15;
            t*=t; t*=t; t*=t; t*=t; t*=t;
        }
        color[0] = t;
        t = t * t;
        color[1] = .85*t;
        t = t * t;
        color[2] = .6*t;
    }
    void fillRect(GL2 gl, int x, int y, int w, int h) {
        gl.glBegin(GL2.GL_QUADS);
        gl.glVertex2i(x, y);
        gl.glVertex2i(x, y+h);
        gl.glVertex2i(x+w, y+h);
        gl.glVertex2i(x+w, y);
        gl.glEnd();
    }

    public void init(GL2 gl) {
        window = Fv3Screen.Current();
        windowWidth = (int)window.width;
        windowHeight = (int)window.height;
        initBuffer();
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(0.0f, windowWidth, windowHeight, 0.0f, -1.0f, 1.0f);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glViewport(0,0,windowWidth,windowHeight);
        gl.glClearColor(1.0f,0.95f,0.85f,0.0f);
    }
    public void display(GL2 gl) {
        if (pass >= N*N)
            return;
        else {
            gl.glDrawBuffer(GL2.GL_BACK); // draw into the back buffer

            int s = ss[pass];
            for (int y = sy[pass] ; y < windowHeight ; y += N){
                for (int x = sx[pass] ; x < windowWidth  ; x += N) {

                    computeColor( (double)x/RES, (double)y/RES*windowHeight/windowWidth);

                    gl.glColor3ub( (byte)(255*color[0]), (byte)(255*color[1]), (byte)(255*color[2]));

                    fillRect(gl,x-s/2,y-s/2,s,s);
                }
            }
            pass++;

            // copy the back buffer to the front:

            gl.glReadPixels(0,0,windowWidth,windowHeight,GL2.GL_ABGR_EXT,GL2.GL_UNSIGNED_BYTE, buffer);
            gl.glDrawBuffer(GL2.GL_FRONT);
            gl.glDrawPixels(windowWidth,windowHeight,GL2.GL_ABGR_EXT,GL2.GL_UNSIGNED_BYTE,buffer);
        }
    }
    public void keyReleased(KeyEvent e) {
        char key = e.getKeyChar();
        switch (key){
        case ' ':
            xOrigin = 0;
            yOrigin = 0;
            scale = .25;
            pass = 0;
            System.err.println(String.format("%s: Mandelbrot x=%f, y=%f, s=%f",Thread.currentThread().getName(),xOrigin,yOrigin,scale));
            break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            limit = (32 << (key - '0'));
            pass = 0;
            System.err.println(String.format("%s: Mandelbrot limit=%d",Thread.currentThread().getName(),limit));
            break;
        default:
            super.keyReleased(e);
            break;
        }
    }
    public void mousePressed(MouseEvent e) {
        double X = (double)e.getX() / windowWidth;
        double Y = (double)e.getY() / windowWidth;

        switch (e.getButton()){
        case 1:
            scale *= 2;
            xOrigin += xOrigin - X;
            yOrigin += yOrigin - Y;
            pass = 0;
            System.err.println(String.format("%s: Mandelbrot x=%f, y=%f, s=%f",Thread.currentThread().getName(),xOrigin,yOrigin,scale));
            break;
        case 2:
        case 3:
            xOrigin += (X - xOrigin) / 2;
            yOrigin += (Y - yOrigin) / 2;
            scale /= 2;
            pass = 0;
            System.err.println(String.format("%s: Mandelbrot x=%f, y=%f, s=%f",Thread.currentThread().getName(),xOrigin,yOrigin,scale));
            break;
        }
    }
}
